package com.wg.db.sql;

import com.wg.db.exception.DataException;
import com.wg.db.schema.SchemaColumn;
import com.wg.db.schema.SchemaTable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * MySQL 数据库 SQL 生成工具类<br>
 * 提供插入、更新、删除、查询等常用 SQL 语句的生成功能，支持预编译参数化查询，防止 SQL 注入。
 *
 * @author 少爷123
 */
public class MySqlExpert {

    // ========================== 插入操作 ==========================

    /**
     * 生成单条数据插入 SQL（参数化查询）
     *
     * @param em     表结构元数据
     * @param entity 要插入的实体对象
     * @param <T>    实体类型
     * @return 包含 SQL 文本和参数的 {@link SqlStatement}
     * @throws DataException 若 entity 为 null 或表元数据无效时抛出
     */
    public static <T> SqlStatement genInsertSql(SchemaTable em, T entity) {
        validateEntity(em, entity);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入 SQL");
        }

        List<Object> args = new ArrayList<>(columns.size());
        StringBuilder sb = new StringBuilder(256)
                .append("INSERT INTO ")
                .append(em.getRealTableName())
                .append(" (")
                .append(buildColumnNames(columns))
                .append(") VALUES (")
                .append(buildValuePlaceholders(columns.size()));

        for (SchemaColumn column : columns) {
            args.add(column.getValue(entity));
        }

        sb.append(")");
        return createSqlStatement(sb.toString(), args);
    }

    public static <T> String genInsertSqlCommandText(SchemaTable em) {
        validateTable(em);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入 SQL");
        }

        StringBuilder sb = new StringBuilder(256)
                .append("INSERT INTO ")
                .append(em.getRealTableName())
                .append(" (")
                .append(buildColumnNames(columns))
                .append(") VALUES (")
                .append(buildValuePlaceholders(columns.size()));

        sb.append(")");
        return sb.toString();
    }

    public static <T> Object[] genInsertSqlArgs(SchemaTable em, T entity) {
        validateEntity(em, entity);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入 SQL");
        }

        List<Object> args = new ArrayList<>(columns.size());
        for (SchemaColumn column : columns) {
            args.add(column.getValue(entity));
        }
        return args.toArray();
    }

    // ========================== 插入或更新（Upsert） ==========================

    /**
     * 生成插入或更新 SQL（若主键冲突则更新非主键字段，参数化查询）
     *
     * @param em     表结构元数据
     * @param entity 要插入或更新的实体对象
     * @param <T>    实体类型
     * @return 包含 SQL 文本和参数的 {@link SqlStatement}
     * @throws DataException 若 entity 为 null、表元数据无效或无主键时抛出
     */
    public static <T> SqlStatement genInsertOrUpdateSql(SchemaTable em, T entity) {
        validateEntity(em, entity);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入或更新 SQL");
        }
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("插入或更新操作需要主键字段");
        }

        List<Object> args = new ArrayList<>(columns.size() * 2); // 最多可能两倍参数（插入+更新）
        StringBuilder sb = new StringBuilder(512)
                .append("INSERT INTO ")
                .append(em.getRealTableName())
                .append(" (")
                .append(buildColumnNames(columns))
                .append(") VALUES (")
                .append(buildValuePlaceholders(columns.size()))
                .append(")");

        // 构建 ON DUPLICATE KEY UPDATE 部分（排除主键）
        StringBuilder updateClause = new StringBuilder(256);
        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                updateClause.append('`').append(column.getName()).append("`=?,");
                args.add(column.getValue(entity));
            }
        }
        if (updateClause.length() > 0) {
            updateClause.setCharAt(updateClause.length() - 1, ' '); // 替换末尾逗号为空格
            sb.append(" ON DUPLICATE KEY UPDATE ").append(updateClause);
        }

        return createSqlStatement(sb.toString(), args);
    }

    public static <T> String genInsertOrUpdateSqlCommandText(SchemaTable em) {
        validateTable(em);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入或更新 SQL");
        }
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("插入或更新操作需要主键字段");
        }

        StringBuilder sb = new StringBuilder(512)
                .append("INSERT INTO ")
                .append(em.getRealTableName())
                .append(" (")
                .append(buildColumnNames(columns))
                .append(") VALUES (")
                .append(buildValuePlaceholders(columns.size()))
                .append(")");

        // 构建 ON DUPLICATE KEY UPDATE 部分（排除主键）
        StringBuilder updateClause = new StringBuilder(256);
        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                updateClause.append('`').append(column.getName()).append("`=?,");
            }
        }
        if (updateClause.length() > 0) {
            updateClause.setCharAt(updateClause.length() - 1, ' '); // 替换末尾逗号为空格
            sb.append(" ON DUPLICATE KEY UPDATE ").append(updateClause);
        }

        return sb.toString();
    }

    public static <T> Object[] genInsertOrUpdateSqlArgs(SchemaTable em, T entity) {
        validateEntity(em, entity);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成插入或更新 SQL");
        }

        List<Object> args = new ArrayList<>(columns.size() * 2); // 最多可能两倍参数（插入+更新）
        for (SchemaColumn column : columns) {
            args.add(column.getValue(entity));
        }

        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                args.add(column.getValue(entity));
            }
        }

        return args.toArray();
    }


    // ========================== 更新操作 ==========================

    /**
     * 生成单条数据更新 SQL（根据主键更新，参数化查询）
     *
     * @param em     表结构元数据
     * @param entity 要更新的实体对象
     * @param <T>    实体类型
     * @return 包含 SQL 文本和参数的 {@link SqlStatement}
     * @throws DataException 若 entity 为 null、表元数据无效、无主键或主键值为空时抛出
     */
    public static <T> SqlStatement genUpdateSql(SchemaTable em, T entity) {
        validateEntity(em, entity);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成更新 SQL");
        }
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("更新操作需要主键字段");
        }
        Object primaryKeyValue = primaryKey.getValue(entity);
        if (primaryKeyValue == null) {
            throw new DataException("主键字段 [" + primaryKey.getName() + "] 值不能为 null");
        }

        List<Object> args = new ArrayList<>(columns.size()); // 主键值最后添加
        StringBuilder sb = new StringBuilder(512)
                .append("UPDATE ")
                .append(em.getRealTableName())
                .append(" SET ");

        // 构建 SET 子句（排除主键）
        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                sb.append('`').append(column.getName()).append("`=?,");
                args.add(column.getValue(entity));
            }
        }
        sb.setCharAt(sb.length() - 1, ' '); // 替换末尾逗号为空格

        // 添加 WHERE 主键条件
        sb.append("WHERE `").append(primaryKey.getName()).append("`=?");
        args.add(primaryKeyValue);

        return createSqlStatement(sb.toString(), args);
    }

    public static <T> String genUpdateSqlCommandText(SchemaTable em) {
        validateTable(em);

        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成更新 SQL");
        }

        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("更新操作需要主键字段");
        }

        StringBuilder sb = new StringBuilder(512)
                .append("UPDATE ")
                .append(em.getRealTableName())
                .append(" SET ");
        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                sb.append('`').append(column.getName()).append("`=?,");
            }
        }
        sb.setCharAt(sb.length() - 1, ' '); // 替换末尾逗号为空格
        sb.append("WHERE `").append(primaryKey.getName()).append("`=?");
        return sb.toString();
    }

    public static <T> Object[] genUpdateSqlArgs(SchemaTable em, T entity) {
        validateEntity(em, entity);

        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成更新 SQL");
        }

        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("更新操作需要主键字段");
        }

        Object primaryKeyValue = primaryKey.getValue(entity);
        if (primaryKeyValue == null) {
            throw new DataException("主键字段 [" + primaryKey.getName() + "] 值不能为 null");
        }

        List<Object> args = new ArrayList<>(columns.size()); // 主键值最后添加
        for (SchemaColumn column : columns) {
            if (!column.isKey()) { // 非主键字段才更新
                args.add(column.getValue(entity));
            }
        }
        args.add(primaryKeyValue);
        return args.toArray();
    }

    // ========================== 删除操作 ==========================

    /**
     * 生成单条数据删除 SQL（根据主键删除，参数化查询）
     *
     * @param em     表结构元数据
     * @param entity 要删除的实体对象（仅需包含主键值）
     * @param <T>    实体类型
     * @return 包含 SQL 文本和参数的 {@link SqlStatement}
     * @throws DataException 若 entity 为 null、表元数据无效、无主键或主键值为空时抛出
     */
    public static <T> SqlStatement genDeleteSql(SchemaTable em, T entity) {
        validateEntity(em, entity);
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("删除操作需要主键字段");
        }
        Object primaryKeyValue = primaryKey.getValue(entity);
        if (primaryKeyValue == null) {
            throw new DataException("主键字段 [" + primaryKey.getName() + "] 值不能为 null");
        }

        List<Object> args = new ArrayList<>(1);
        StringBuilder sb = new StringBuilder(256)
                .append("DELETE FROM ")
                .append(em.getRealTableName())
                .append(" WHERE `").append(primaryKey.getName()).append("`=?");
        args.add(primaryKeyValue);

        return createSqlStatement(sb.toString(), args);
    }

    public static <T> String genDeleteSqlCommandText(SchemaTable em) {
        validateTable(em);
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("删除操作需要主键字段");
        }
        return "DELETE FROM " + em.getRealTableName() + " WHERE `" + primaryKey.getName() + "`=?";
    }

    public static <T> Object[] genDeleteSqlArgs(SchemaTable em, T entity) {
        validateEntity(em, entity);

        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("删除操作需要主键字段");
        }

        Object primaryKeyValue = primaryKey.getValue(entity);
        if (primaryKeyValue == null) {
            throw new DataException("主键字段 [" + primaryKey.getName() + "] 值不能为 null");
        }

        return new Object[]{primaryKeyValue};
    }

    // ========================== 查询操作 ==========================

    /**
     * 生成根据分组字段查询的 SQL（参数化查询）
     *
     * @param em 表结构元数据（需包含分组字段定义）
     * @return 包含 SQL 文本的字符串（无参数，需手动绑定参数）
     * @throws DataException 若表元数据无效时抛出
     */
    public static String genSelectByGroupId(SchemaTable em) {
        validateTable(em);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成查询 SQL");
        }
        SchemaColumn groupField = em.getGroupField();
        if (groupField == null) {
            throw new DataException("表 [" + em.getRealTableName() + "] 未定义分组字段");
        }

        StringBuilder sb = new StringBuilder(256)
                .append("SELECT ")
                .append(buildColumnNames(columns))
                .append(" FROM ")
                .append(em.getRealTableName())
                .append(" WHERE `")
                .append(groupField.getName()).append("`=?");
        return sb.toString();
    }

    /**
     * 生成根据主键查询单条数据的 SQL（参数化查询）
     *
     * @param em 表结构元数据（需包含主键定义）
     * @return 包含 SQL 文本的字符串（无参数）
     * @throws DataException 若 entity 为 null、表元数据无效、无主键或主键值为空时抛出
     */
    public static <T> String genSelectSql(SchemaTable em) {
        validateTable(em);
        SchemaColumn primaryKey = em.getPrimaryField();
        if (primaryKey == null) {
            throw new DataException("查询操作需要主键字段");
        }

        StringBuilder sb = new StringBuilder(256)
                .append("SELECT ")
                .append(buildColumnNames(em.getColumns()))
                .append(" FROM ")
                .append(em.getRealTableName())
                .append(" WHERE `").append(primaryKey.getName()).append("`=?");

        return sb.toString();
    }

    /**
     * 生成查询表所有数据的 SQL（参数化查询）
     *
     * @param em 表结构元数据（需包含有效列定义）
     * @return 包含 SQL 文本的字符串（无参数）
     * @throws DataException 若表元数据无效时抛出
     */
    public static String genSelectAllSql(SchemaTable em) {
        validateTable(em);
        List<SchemaColumn> columns = em.getColumns();
        if (columns.isEmpty()) {
            throw new DataException("表 [" + em.getRealTableName() + "] 无有效列，无法生成查询 SQL");
        }

        StringBuilder sb = new StringBuilder(256)
                .append("SELECT ")
                .append(buildColumnNames(columns))
                .append(" FROM ")
                .append(em.getRealTableName());
        return sb.toString();
    }

    // ========================== 私有工具方法 ==========================

    /**
     * 校验实体和表元数据有效性
     *
     * @param em     表结构元数据
     * @param entity 实体对象
     * @param <T>    实体类型
     * @throws DataException 若 entity 或 em 为 null 时抛出
     */
    private static <T> void validateEntity(SchemaTable em, T entity) {
        Objects.requireNonNull(em, "表结构元数据不能为 null");
        Objects.requireNonNull(entity, "实体对象不能为 null");
        validateTable(em);
    }

    /**
     * 校验表元数据有效性
     *
     * @param em 表结构元数据
     * @throws DataException 若 em 为 null 或表名为空时抛出
     */
    private static void validateTable(SchemaTable em) {
        if (em == null) {
            throw new DataException("表结构元数据不能为 null");
        }
        String tableName = em.getRealTableName();
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new DataException("表名不能为空");
        }
    }

    /**
     * 构建列名列表（自动添加反引号转义关键字）
     *
     * @param columns 列元数据列表
     * @return 逗号分隔的列名字符串（如：`id`, `name`）
     */
    private static String buildColumnNames(List<SchemaColumn> columns) {
        if (columns == null || columns.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder(columns.size() * 20); // 预分配容量
        for (int i = 0; i < columns.size(); i++) {
            SchemaColumn column = columns.get(i);
            sb.append('`').append(column.getName()).append('`');
            if (i != columns.size() - 1) {
                sb.append(", ");
            }
        }
        return sb.toString();
    }

    /**
     * 构建值占位符列表（? 占位符）
     *
     * @param count 占位符数量
     * @return 逗号分隔的 ? 字符串（如：?, ?, ?）
     */
    private static String buildValuePlaceholders(int count) {
        if (count <= 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder(count * 2); // 每个?占1位，逗号占1位
        for (int i = 0; i < count; i++) {
            sb.append('?');
            if (i != count - 1) {
                sb.append(", ");
            }
        }
        return sb.toString();
    }

    /**
     * 创建 {@link SqlStatement} 对象
     *
     * @param commandText SQL 文本
     * @param params      参数数组
     * @return 包装后的 {@link SqlStatement}
     */
    private static SqlStatement createSqlStatement(String commandText, List<Object> params) {
        SqlStatement sqlStatement = new SqlStatement();
        sqlStatement.setCommandText(commandText);
        sqlStatement.setParams(params.toArray());
        return sqlStatement;
    }
}